unit QBMyWindow;
{$I QBuilderVerCtrl.inc}
interface

uses
  {$IFDEF VCL16}
    System.Classes,
    System.SysUtils,
  {$ELSE}
    Classes,
    SysUtils,
  {$ENDIF}
  QBWindow,
  QBParser,
  QBStringTypes;

const
  MySQLKeywords = 'ACTION,ADD,AGGREGATE,ALL,ALTER,AFTER,AND,AS,ASC,' +
    'AVG_ROW_LENGTH,AUTO_INCREMENT,BETWEEN,BIGINT,BIT,BINARY,BLOB,BOOL,' +
    'BOTH,BY,CASCADE,CASE,CHAR,CHARACTER,CHANGE,CHECK,CHECKSUM,COLUMN,' +
    'COLUMNS,COMMENT,CONSTRAINT,CREATE,CROSS,CURRENT_DATE,CURRENT_TIME,' +
    'CURRENT_TIMESTAMP,DATA,DATABASE,DATABASES,DATE,DATETIME,DAY,' +
    'DAY_HOUR,DAY_MINUTE,DAY_SECOND,DAYOFMONTH,DAYOFWEEK,DAYOFYEAR,DEC,' +
    'DECIMAL,DEFAULT,DELAYED,DELAY_KEY_WRITE,DELETE,DESC,DESCRIBE,DISTINCT,' +
    'DISTINCTROW,DOUBLE,DROP,END,ELSE,ESCAPE,ESCAPED,ENCLOSED,ENUM,EXPLAIN,' +
    'EXISTS,FIELDS,FILE,FIRST,FLOAT,FLOAT4,FLOAT8,FLUSH,FOREIGN,FROM,FOR,' +
    'FULL,FUNCTION,GLOBAL,GRANT,GRANTS,GROUP,HAVING,HEAP,HIGH_PRIORITY,' +
    'HOUR,HOUR_MINUTE,HOUR_SECOND,HOSTS,IDENTIFIED,IGNORE,IN,INDEX,INFILE,' +
    'INNER,INSERT,INSERT_ID,INT,INTEGER,INTERVAL,INT1,INT2,INT3,INT4,INT8,' +
    'INTO,IS,ISAM,JOIN,KEY,KEYS,KILL,LAST_INSERT_ID,LEADING,LEFT,LENGTH,' +
    'LIKE,LINES,LIMIT,LOAD,LOCAL,LOCK,LOGS,LONG,LONGBLOB,LONGTEXT,' +
    'LOW_PRIORITY,MAX,MAX_ROWS,MATCH,MEDIUMBLOB,MEDIUMTEXT,MEDIUMINT,' +
    'MIDDLEINT,MIN_ROWS,MINUTE,MINUTE_SECOND,MODIFY,MONTHNAME,MYISAM,' +
    'NATURAL,NUMERIC,NO,NOT,NULL,ON,OPTIMIZE,OPTION,OPTIONALLY,OR,ORDER,' +
    'OUTER,OUTFILE,PACK_KEYS,PARTIAL,PRECISION,PRIMARY,PROCEDURE,PROCESS,' +
    'PROCESSLIST,PRIVILEGES,READ,REAL,REFERENCES,RELOAD,REGEXP,RENAME,' +
    'REPLACE,RESTRICT,RETURNS,REVOKE,RIGHT,RLIKE,ROW,ROWS,SECOND,SELECT,SET,SHOW,' +
    'SHUTDOWN,SMALLINT,SONAME,SQL_BIG_TABLES,SQL_BIG_SELECTS,' +
    'SQL_LOW_PRIORITY_UPDATES,SQL_LOG_OFF,SQL_LOG_UPDATE,SQL_SELECT_LIMIT,' +
    'SQL_SMALL_RESULT,SQL_BIG_RESULT,SQL_WARNINGS,STRAIGHT_JOIN,STARTING,' +
    'STATUS,STRING,TABLE,TABLES,TEMPORARY,TERMINATED,TEXT,THEN,TIME,' +
    'TIMESTAMP,TINYBLOB,TINYTEXT,TINYINT,TRAILING,TO,TYPE,USE,USING,UNIQUE,' +
    'UNLOCK,UNSIGNED,UPDATE,USAGE,VALUES,VARCHAR,VARIABLES,VARYING,' +
    'VARBINARY,WITH,WRITE,WHEN,WHERE,YEAR_MONTH,ZEROFILL,UNION'{,YEAR};

  MySQLFunctions =
    'ABS,ACOS,ADDDATE,ADDTIME,AES_DECRYPT,AES_ENCRYPT,AREA,ASBINARY,' +
    'ASIN,ASTEXT,ASWKB,ASWKT,ATAN,ATAN2,BENCHMARK,BIN,BIT_AND,BIT_COUNT,' +
    'BIT_LENGTH,BIT_OR,BIT_XOR,CAST,CEIL,CEILING,CENTROID,' +
    'CHARACTER_LENGTH,CHAR_LENGTH,COALESCE,COERCIBILITY,COMPRESS,' +
    'CONCAT,CONCAT_WS,CONNECTION_ID,CONV,CONVERT_TZ,COS,COT,COUNT,' +
    'CRC32,CROSSES,CURDATE,CURTIME,DATE,DATEDIFF,DATE_ADD,DATE_FORMAT,' +
    'DATE_SUB,DAYNAME,DAYOFMONTH,DAYOFWEEK,DAYOFYEAR,DECODE,DEGREES,' +
    'DES_DECRYPT,DES_ENCRYPT,DIMENSION,DISJOINT,ELT,ENCODE,ENCRYPT,' +
    'ENDPOINT,ENVELOPE,EQUALS,EXP,EXPORT_SET,EXTERIORRING,EXTRACT,FIELD,' +
    'FIND_IN_SET,FLOOR,FORMAT,FOUND_ROWS,FROM_DAYS,FROM_UNIXTIME,' +
    'GEOMCOLLFROMTEXT,GEOMCOLLFROMWKB,GEOMETRYCOLLECTIONFROMTEXT,' +
    'GEOMETRYCOLLECTIONFROMWKB,GEOMETRYFROMTEXT,GEOMETRYFROMWKB,' +
    'GEOMETRYN,GEOMETRYTYPE,GEOMFROMTEXT,GEOMFROMWKB,GET_LOCK,' +
    'GLENGTH,GREATEST,GROUP_CONCAT,GROUP_UNIQUE_USERS,HEX,IFNULL,' +
    'INET_ATON,INET_NTOA,INSTR,INTERIORRINGN,INTERSECTS,ISCLOSED,' +
    'ISEMPTY,ISNULL,ISSIMPLE,IS_FREE_LOCK,IS_USED_LOCK,LAST_DAY,' +
    'LAST_INSERT_ID,LCASE,LEAST,LEFT,LENGTH,LINEFROMTEXT,LINEFROMWKB,' +
    'LINESTRINGFROMTEXT,LINESTRINGFROMWKB,LN,LOAD_FILE,LOCATE,LOG,' +
    'LOG10,LOG2,LOWER,LPAD,LTRIM,MAKEDATE,MAKETIME,MAKE_SET,' +
    'MASTER_POS_WAIT,MAX,MBRCONTAINS,MBRDISJOINT,MBREQUAL,MBRINTERSECTS,' +
    'MBROVERLAPS,MBRTOUCHES,MBRWITHIN,MD5,MID,MIN,MLINEFROMTEXT,' +
    'MLINEFROMWKB,MONTHNAME,MPOINTFROMTEXT,MPOINTFROMWKB,MPOLYFROMTEXT,' +
    'MPOLYFROMWKB,MULTILINESTRINGFROMTEXT,MULTILINESTRINGFROMWKB,' +
    'MULTIPOINTFROMTEXT,MULTIPOINTFROMWKB,MULTIPOLYGONFROMTEXT,' +
    'MULTIPOLYGONFROMWKB,NOW,NULLIF,NUMGEOMETRIES,NUMINTERIORRINGS,' +
    'NUMPOINTS,OCT,OCTET_LENGTH,ORD,OVERLAPS,PERIOD_ADD,PERIOD_DIFF,' +
    'PI,POINTFROMTEXT,POINTFROMWKB,POINTN,POLYFROMTEXT,POLYFROMWKB,' +
    'POLYGONFROMTEXT,POLYGONFROMWKB,POSITION,POW,POWER,QUOTE,RADIANS,' +
    'RAND,RELEASE_LOCK,REVERSE,ROUND,ROW_COUNT,RPAD,RTRIM,SEC_TO_TIME,' +
    'SESSION_USER,SHA,SHA1,SIGN,SIN,SOUNDEX,SPACE,SQRT,SRID,STARTPOINT,' +
    'STD,STDDEV,STRCMP,STR_TO_DATE,SUBDATE,SUBSTR,SUBSTRING,' +
    'SUBSTRING_INDEX,SUBTIME,SUM,SYSDATE,SYSTEM_USER,TAN,TIMEDIFF,' +
    'TIME_FORMAT,TIME_TO_SEC,TOUCHES,TO_DAYS,TRIM,UCASE,UNCOMPRESS,' +
    'UNCOMPRESSED_LENGTH,UNHEX,UNIQUE_USERS,UNIX_TIMESTAMP,UPPER,' +
    'UUID,VARIANCE,VERSION,WEEKDAY,WEEKOFYEAR,WITHIN,X,Y,YEARWEEK,YEAR,MONTH,LEFT';

  MySQLAggregates = 'COUNT,AVG,MIN,MAX,SUM,GROUP_CONCAT,VARIANCE,STD,STDDEV,' +
    'BIT_OR,BIT_AND';
  MySQLCommaCompareOperators = '=,<>,!=,<,<=,>,>=," LIKE "';

type

  TQBMySQLSelectOption = (mysoStraightJoin, mysoSQLSmallResult,
    mysoSQLBigResult, mysoSQLBufferResult, mysoHighPriority);
  TQBMySQLSelectOptions = set of TQBMySQLSelectOption;

  TQBMySQLFilter = (myfoNone, myfoDistinct, myfoDistinctRow, myfoAll);

  TQBMySQLOptions = class(TPersistent)
  private
    FLimitRows: integer;
    FLimitOffset: integer;
    FSelectOptions: TQBMySQLSelectOptions;
    FFilter: TQBMySQLFilter;
    FParser: TQBSelectParser;
    function GetLimitScript: aqbString;
    function GetOptionsScript: aqbString;
    function GetFilterScript: aqbString;
  public
    constructor Create(AParser: TQBSelectParser);
    procedure Assign(Source: TPersistent); override;
    procedure Clear;

    property Filter: TQBMySQLFilter read FFilter write FFilter;
    property LimitOffset: integer read FLimitOffset write FLimitOffset;
    property LimitRows: integer read FLimitRows write FLimitRows;
    property SelectOptions: TQBMySQLSelectOptions read FSelectOptions
      write FSelectOptions;

    property FilterScript: aqbString read GetFilterScript;
    property LimitScript: aqbString read GetLimitScript;
    property OptionsScript: aqbString read GetOptionsScript;
    property Parser: TQBSelectParser read FParser;
  end;

  TQBMySQLScanner = class(TQBSQLScanner)
  protected
    function IsTerminator(Ch: aqbChar; const S: aqbString;
      BeginQuote, NextChar: aqbChar): boolean; override;
    function GetIdentQuote: aqbChar; override;
    function GetCommaCompareOperators: aqbString; override;
    function GetProcessAliases: boolean; override;
    function GetCommaKeywords: aqbString; override;
    function GetCommaFunctions: aqbString; override;
    procedure DefineBooleans; override;
  public
    function NeedQuotes(const Ident: aqbString): boolean; override;
    function IsIdentQuote(Ch: aqbChar): boolean; override;
  end;

  TQBMySelectParser = class(TQBSelectParser)
  private
    FMySQLOptions: TQBMySQLOptions;
    procedure SetMySQLOptions(const Value: TQBMySQLOptions);
  protected
    function CreateScanner: TQBSQLScanner; override;
    procedure DefineOperators; override;
    function GetCommaAggregates: aqbString; override;
    function GetExpression: aqbString; override;
    procedure DoParseSelectClause; override;
    procedure DoParseOptions; virtual;
    procedure DoParseFilter; virtual;
    procedure DoParseAppendix; override;
    function GetDistinct: boolean; override;
    procedure SetDistinct(const Value: boolean); override;
    function GetCaseSensitiveIdentifiers: boolean; override;
    procedure DoParseJoinDef; override;
  public
    constructor Create(AParent: TPersistent); override;
    destructor Destroy; override;
    procedure Clear; override;
    property MySQLOptions: TQBMySQLOptions read FMySQLOptions write SetMySQLOptions;
    function IsFullOuterJoinSupported: boolean; override;    
  end;

  TMySQLQueryBuilder = class(TFullQueryBuilder {TCustomQueryBuilder})
  public
    function CreateParser(AParent: TPersistent): TQBSelectParser; override;
  published
    property About;
    property Align;
    property Aggregates;
    property Functions;
    property Keywords;
    property Operators;
    property Options;
    property Palette;
    property QuoteIdentifiers default True;
    property ScriptFormat;
    property Storage;
    property Style;
    property TabHeight;
    property VisibleTabs;
    property _Version;
    // ayz
    property WorkAreaPopupMenu;
    property TabsPopupMenu;
    // \ayz

    property OnStartDrag;
    property OnDragOver;
    property OnDragDrop;
    property OnEndDrag;
    {$IFNDEF EC_UNICODE}
    property OnGetTableFields;
    property OnParserError;
    {$ELSE}
    property OnGetTableFieldsW;
    property OnParserErrorW;
    {$ENDIF}
    property OnAddRemoveTable;
    property OnAddRemoveLink;
    property OnEnterProcParameter;
  end;

implementation

const
  sSelect = 'SELECT';
  sLimit = 'LIMIT';
  sDistinct = 'DISTINCT';
  sDistinctRow = 'DISTINCTROW';
  sAll = 'ALL';
  sStraightJoin = 'STRAIGHT_JOIN';
  sSQLSmallResult = 'SQL_SMALL_RESULT';
  sSQLBigResult = 'SQL_BIG_RESULT';
  sSQLBufferResult = 'SQL_BUFFER_RESULT';
  sHighPriority = 'HIGH_PRIORITY';

  { TQBMySQLScanner }

procedure TQBMySQLScanner.DefineBooleans;
begin
  // not supported bu server
end;

function TQBMySQLScanner.GetCommaCompareOperators: aqbString;
begin
  Result := MySQLCommaCompareOperators;
end;

function TQBMySQLScanner.GetCommaFunctions: aqbString;
begin
  Result := MySQLFunctions;
end;

function TQBMySQLScanner.GetCommaKeywords: aqbString;
begin
  Result := MySQLKeywords;
end;

function TQBMySQLScanner.GetIdentQuote: aqbChar;
begin
  Result := '`';
end;

function TQBMySQLScanner.GetProcessAliases: boolean;
begin
  Result := QuoteIdentifiers;
end;

function TQBMySQLScanner.IsIdentQuote(Ch: aqbChar): boolean;
begin
  Result := Ch = '`';
end;

function TQBMySQLScanner.IsTerminator(Ch: aqbChar; const S: aqbString;
  BeginQuote, NextChar: aqbChar): boolean;
var
  I: integer;
begin
  Result := (Ch = BeginQuote) and (NextChar <> BeginQuote);
  if not Result then
    Exit;

  I := Length(S);
  while (I > 0) and (S[I] = Ch) do
    Dec(I);
  if (I > 0) and (S[I] = '\') then
    Inc(I);
  Result := not Odd(Length(S) - I);
end;

function TQBMySQLScanner.NeedQuotes(const Ident: aqbString): boolean;
begin
  //pav
  Result := not ((Length(Ident) > 1) and IsIdentQuote(Ident[1]) and
    IsIdentQuote(Ident[Length(Ident)]));
  //\pav fix CR27804
end;

{ TQBMySelectParser }

procedure TQBMySelectParser.Clear;
begin
  inherited Clear();
  FMySQLOptions.Clear();
end;

constructor TQBMySelectParser.Create(AParent: TPersistent);
begin
  inherited Create(AParent);
  FMySQLOptions := TQBMySQLOptions.Create(Self);
end;

function TQBMySelectParser.CreateScanner: TQBSQLScanner;
begin
  Result := TQBMySQLScanner.Create();
end;

procedure TQBMySelectParser.DefineOperators;
var
  Operator: TQBOperator;
begin
  Operators.Clear();

  Operator := Operators.Add();
  Operator.Name := '=';
  Operator.Caption := '=';

  Operator := Operators.Add();
  Operator.Name := '<>';
  Operator.Caption := '<>';

  Operator := Operators.Add();
  Operator.Name := '!=';
  Operator.Caption := '!=';

  Operator := Operators.Add();
  Operator.Name := '<';
  Operator.Caption := '<';

  Operator := Operators.Add();
  Operator.Name := '>';
  Operator.Caption := '>';

  Operator := Operators.Add();
  Operator.Name := '<=';
  Operator.Caption := '<=';

  Operator := Operators.Add();
  Operator.Name := '>=';
  Operator.Caption := '>=';

  Operator := Operators.Add();
  Operator.Name := 'LIKE';
  Operator.Caption := 'LIKE';

  Operator := Operators.Add();
  Operator.Name := 'NOT LIKE';
  Operator.Caption := 'NOT LIKE';

  Operator := Operators.Add();
  Operator.Name := 'IN';
  Operator.Caption := 'IN';
  Operator.Prefix := '(';
  Operator.Postfix := ')';
  Operator.CommaList := True;

  Operator := Operators.Add();
  Operator.Name := 'NOT IN';
  Operator.Caption := 'NOT IN';
  Operator.Prefix := '(';
  Operator.Postfix := ')';
  Operator.CommaList := True;

  Operator := Operators.Add();
  Operator.Name := 'BETWEEN';
  Operator.Caption := 'BETWEEN';
  Operator.Relate := 'AND';
  Operator.Count := 2;

  Operator := Operators.Add();
  Operator.Name := 'NOT BETWEEN';
  Operator.Caption := 'NOT BETWEEN';
  Operator.Relate := 'AND';
  Operator.Count := 2;

  Operator := Operators.Add();
  Operator.Name := 'IS NULL';
  Operator.Caption := 'IS NULL';
  Operator.Count := 0;

  Operator := Operators.Add();
  Operator.Name := 'IS NOT NULL';
  Operator.Caption := 'IS NOT NULL';
  Operator.Count := 0;
end;

destructor TQBMySelectParser.Destroy;
begin
  FMySQLOptions.Free();
  inherited Destroy();
end;

procedure TQBMySelectParser.DoParseAppendix;
var
  LimitValue: integer;
begin
  if CompareText(Scanner.Token, 'LIMIT') = 0 then
  begin
    Scanner.GetNextToken();
    if tkInteger in Scanner.TokenKind then
    begin
      LimitValue := StrToInt(Scanner.Token);
      Scanner.GetNextToken();
      if Scanner.Token = ',' then
      begin
        Scanner.GetNextToken();
        if tkInteger in Scanner.TokenKind then
        begin
          FMySQLOptions.LimitOffset := LimitValue;
          FMySQLOptions.LimitRows := StrToInt(Scanner.Token);
        end
        else
        begin
          State := ssError;
          Exit;
        end;
      end
      else
      begin
        FMySQLOptions.LimitOffset := 0;
        FMySQLOptions.LimitRows := LimitValue;
      end;
    end
    else
    begin
      State := ssError;
      Exit;
    end;
    State := ssEnd;
  end
  else
    inherited DoParseAppendix();
end;

procedure TQBMySelectParser.DoParseFilter;
begin
  State := ssFieldDefs;
  if CompareText(Scanner.Token, sDistinct) = 0 then
    FMySQLOptions.Filter := myfoDistinct
  else if CompareText(Scanner.Token, sDistinctRow) = 0 then
    FMySQLOptions.Filter := myfoDistinctRow
  else if CompareText(Scanner.Token, sAll) = 0 then
    FMySQLOptions.Filter := myfoAll
  else
    State := ssError;
  if State <> ssError then
    Scanner.GetNextToken();
end;

procedure TQBMySelectParser.DoParseJoinDef;
begin
  inherited;
  // table1 join table2 join table3
  if State = ssError then
    State := ssTableDefs;
end;

procedure TQBMySelectParser.DoParseOptions;
  function GetOptionByToken(const Token: aqbString): TQBMySQLSelectOptions;
  begin
    if CompareText(Scanner.Token, sStraightJoin) = 0 then
      Result := [mysoStraightJoin]
    else if CompareText(Scanner.Token, sSQLSmallResult) = 0 then
      Result := [mysoSQLSmallResult]
    else if CompareText(Scanner.Token, sSQLBigResult) = 0 then
      Result := [mysoSQLBigResult]
    else if CompareText(Scanner.Token, sSQLBufferResult) = 0 then
      Result := [mysoSQLBufferResult]
    else if CompareText(Scanner.Token, sHighPriority) = 0 then
      Result := [mysoHighPriority]
    else
      Result := [];
  end;
var
  AlreadyIncluded: TaqbStrings;
begin
  AlreadyIncluded := TaqbStringList.Create();
  try
    while (CompareText(Scanner.Token, sStraightJoin) = 0) or
      (CompareText(Scanner.Token, sSQLSmallResult) = 0) or
      (CompareText(Scanner.Token, sSQLBigResult) = 0) or
      (CompareText(Scanner.Token, sSQLBufferResult) = 0) or
      (CompareText(Scanner.Token, sHighPriority) = 0) do
    begin
      if AlreadyIncluded.IndexOf(QBUpperCase(Scanner.Token)) < 0 then
      begin
        AlreadyIncluded.Add(QBUpperCase(Scanner.Token));
        FMySQLOptions.SelectOptions := FMySQLOptions.SelectOptions +
          GetOptionByToken(Scanner.Token);
      end
      else
      begin
        State := ssError;
        Exit;
      end;
      Scanner.GetNextToken();
    end;
    if (CompareText(Scanner.Token, sDistinct) = 0) or
      (CompareText(Scanner.Token, sDistinctRow) = 0) or
      (CompareText(Scanner.Token, sAll) = 0) then
      DoParseFilter()
    else
      State := ssFieldDefs;
  finally
    AlreadyIncluded.Free();
  end;
end;

procedure TQBMySelectParser.DoParseSelectClause;
begin
  inherited DoParseSelectClause();
  if State = ssError then
  begin
    if (CompareText(Scanner.Token, sStraightJoin) = 0) or
      (CompareText(Scanner.Token, sSQLSmallResult) = 0) or
      (CompareText(Scanner.Token, sSQLBigResult) = 0) or
      (CompareText(Scanner.Token, sSQLBufferResult) = 0) or
      (CompareText(Scanner.Token, sHighPriority) = 0) then
      DoParseOptions()
    else if (CompareText(Scanner.Token, sDistinct) = 0) or
      (CompareText(Scanner.Token, sDistinctRow) = 0) or
      (CompareText(Scanner.Token, sAll) = 0) then
      DoParseFilter()
    else
      State := ssError;
  end
  else if inherited GetDistinct() then
    FMySQLOptions.Filter := myfoDistinct;
end;

function TQBMySelectParser.GetCaseSensitiveIdentifiers: boolean;
begin
  Result := false;
end;

function TQBMySelectParser.GetCommaAggregates: aqbString;
begin
  Result := MySQLAggregates;
end;

function TQBMySelectParser.GetDistinct: boolean;
begin
  Result := FMySQLOptions.Filter = myfoDistinct;
end;

function TQBMySelectParser.GetExpression: aqbString;
begin
  {
    Result := TableDefs.Script;
    if Result <> '' then
      Result := ProcessKeyWord(sSelect) + ' ' + FMySQLOptions.OptionsScript +
        FMySQLOptions.FilterScript + Result + WhereItems.Script +
        GroupByItems.Script + HavingItems.Script + OrderByItems.Script +
        FMySQLOptions.LimitScript;
  }
  Result := TableDefs.Script;
  if Result <> '' then
    case TypeQuery of
      tqSelect: Result := ProcessKeyWord(sSelect) + ' ' + FMySQLOptions.OptionsScript +
        FMySQLOptions.FilterScript + Result + WhereItems.Script +
          GroupByItems.Script + HavingItems.Script + OrderByItems.Script +
          FMySQLOptions.LimitScript;
      tqInsert: Result := ProcessKeyword('INSERT') + ' ' + ProcessKeyword('INTO') +
        Result + ValuesItems.Script;
      tqUpdate: Result := ProcessKeyword('UPDATE') + Result + WhereItems.Script +
        FMySQLOptions.LimitScript;
      tqDelete: Result := ProcessKeyword('DELETE') + Result + WhereItems.Script +
        FMySQLOptions.LimitScript;
    end;
end;

function TQBMySelectParser.IsFullOuterJoinSupported: boolean;
begin
  Result := false;
end;

procedure TQBMySelectParser.SetDistinct(const Value: boolean);
begin
  inherited SetDistinct(Value);
  if Value then
    FMySQLOptions.Filter := myfoDistinct
  else
    FMySQLOptions.Filter := myfoNone;
end;

procedure TQBMySelectParser.SetMySQLOptions(const Value: TQBMySQLOptions);
begin
  if FMySQLOptions <> Value then
    FMySQLOptions.Assign(Value);
end;

{ TMySQLQueryBuilder }

function TMySQLQueryBuilder.CreateParser(AParent: TPersistent): TQBSelectParser;
begin
  Result := TQBMySelectParser.Create(AParent);
end;

{ TQBMySQLOptions }

procedure TQBMySQLOptions.Assign(Source: TPersistent);
begin
  if Source is TQBMySQLOptions then
  begin
    FFilter := TQBMySQLOptions(Source).Filter;
    FLimitRows := TQBMySQLOptions(Source).LimitRows;
    FLimitOffset := TQBMySQLOptions(Source).LimitOffset;
    FSelectOptions := TQBMySQLOptions(Source).SelectOptions;
  end
  else
    inherited Assign(Source);
end;

procedure TQBMySQLOptions.Clear;
begin
  FLimitRows := 0;
  FLimitOffset := 0;
  FSelectOptions := [];
  FFilter := myfoNone;
end;

constructor TQBMySQLOptions.Create(AParser: TQBSelectParser);
begin
  inherited Create();
  FParser := AParser;
end;

function TQBMySQLOptions.GetFilterScript: aqbString;
begin
  if FFilter = myfoDistinct then
  begin
    if Assigned(FParser) then
      Result := FParser.ProcessKeyword(sDistinct)
    else
      Result := sDistinct;
  end
  else if FFilter = myfoDistinctRow then
  begin
    if Assigned(FParser) then
      Result := FParser.ProcessKeyword(sDistinctRow)
    else
      Result := sDistinctRow;
  end
  else if FFilter = myfoAll then
  begin
    if Assigned(FParser) then
      Result := FParser.ProcessKeyword(sAll)
    else
      Result := sAll;
  end
  else
    Result := '';
  if Result <> '' then
    Result := Result + ' ';
end;

function TQBMySQLOptions.GetLimitScript: aqbString;
var
  S: aqbString;
begin
  if Assigned(FParser) then
    S := FParser.ProcessKeyword(sLimit)
  else
    S := sLimit;
  if FLimitOffset > 0 then
    Result := QBFormat(S + ' %s', [IntToStr(FLimitOffset) + ', ' + IntToStr(FLimitRows)])
  else if FLimitRows > 0 then
    Result := QBFormat(S + ' %s', [IntToStr(FLimitRows)])
  else
    Result := '';
end;

function TQBMySQLOptions.GetOptionsScript: aqbString;
begin
  Result := '';
  if mysoStraightJoin in FSelectOptions then
  begin
    if Assigned(FParser) then
      Result := FParser.ProcessKeyword(sStraightJoin)
    else
      Result := sStraightJoin;
  end;
  if mysoSQLSmallResult in FSelectOptions then
  begin
    if Result <> '' then
      Result := Result + ' ';
    if Assigned(FParser) then
      Result := Result + FParser.ProcessKeyword(sSQLSmallResult)
    else
      Result := Result + sSQLSmallResult;
  end;
  if mysoSQLBigResult in FSelectOptions then
  begin
    if Result <> '' then
      Result := Result + ' ';
    if Assigned(FParser) then
      Result := Result + FParser.ProcessKeyword(sSQLBigResult)
    else
      Result := Result + sSQLBigResult;
  end;
  if mysoSQLBufferResult in FSelectOptions then
  begin
    if Result <> '' then
      Result := Result + ' ';
    if Assigned(FParser) then
      Result := Result + FParser.ProcessKeyword(sSQLBufferResult)
    else
      Result := Result + sSQLBufferResult;
  end;
  if mysoHighPriority in FSelectOptions then
  begin
    if Result <> '' then
      Result := Result + ' ';
    if Assigned(FParser) then
      Result := Result + FParser.ProcessKeyword(sHighPriority)
    else
      Result := Result + sHighPriority;
  end;
  if Result <> '' then
    Result := Result + ' ';
end;

end.
